package qc.module.qms.service.ynsyjpt;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import qc.common.core.exception.QCPromptException;
import qc.common.core.json.JsonParserUtil;
import qc.common.core.utils.DateUtil;
import qc.common.core.utils.SqlRunnerUtil;
import qc.common.core.utils.TimeSpanUtil;
import qc.module.platform.dto.dbTable.DBTableFullDto;
import qc.module.qms.dto.station.StationDto;
import qc.module.qms.service.exchange.ExchangeDataService;

import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 云南省汇集平台实时雨量  数据生成器
 *
 * @author QuCheng Tech
 * @since 2024/9/25
 */
@Service
public class YnsyjptPptnDataGenerator {
    @Autowired
    private ExchangeDataService exchangeDataService;

    /**
     * 生成指定站点指定时间段内的上传省汇集平台数据；由于需要原始数据库表信息，需要由外部调用平台接口查询后进行传入
     * 生成数据传入的站点和时间段都是目标交换表中所需的数据，根据每个业务系统处理的不同自动在生成方法中进行原始数据的提取和转换操作
     *
     * @param station     站点信息
     * @param beginTime   生成的数据起始时间
     * @param endTime     生成的数据截止时间
     * @param destDbTable 生成的目标数据库表信息
     * @param srcDbTable  生成的数据来源表信息
     * @return java.lang.String
     * @author QuCheng Tech
     * @since 2024/9/25
     */
    public String generate(StationDto station, Date beginTime, Date endTime, DBTableFullDto destDbTable, DBTableFullDto srcDbTable) throws QCPromptException, SQLException {
        /**
         * 处理的逻辑和步骤
         * 1.判断输入和从输入中获取信息
         *      1.1.站点信息必须有，从站点中获取原始数据的站号、上传使用的水库注册号、appid、appkey
         *      1.2.起止时间段必须正确
         *      1.3.目标数据库表信息，从中获取数据库连接信息和表名
         *      1.4.原始数据库信息，从中获取原始数据库连接信息和表名
         * 2.遍历起止时间点，逐个时间点进行数据处理
         *      2.1.需要每个时间点判断所在月份，得到间隔的分钟数；
         *      2.2.水位数据值处理：
         *      2.3.库容数据值处理：
         *
         * 返回结果为null时表示成功且没有警告，返回结果不为null表示警告信息，如果验证不通过或无法进行处理的抛出异常
         */

        Date now = DateUtil.getNowDate();
        StringBuilder sbResultInfo = new StringBuilder();

        //步骤1.1，站点信息判断和数据获取
        if (station == null)
            throw new QCPromptException("云南省汇集平台实时雨量数据生成器处理，站点信息对象不能为空");
        if (StringUtils.isBlank(station.getStcd()))
            throw new QCPromptException("云南省汇集平台实时雨量数据生成器处理，站点编码不能为空");
        if (StringUtils.isBlank(station.getParams()))
            throw new QCPromptException("云南省汇集平台实时雨量数据生成器处理，站点参数不能为空");
        //此处的stcd为交换数据表中的目标stcd，查询原始数据需要从站点参数中获取
        String stcd = station.getStcd();
        //原始数据对应的stcd，默认与目标stcd相同，从站点参数中获取
        String sourceStcd = stcd;
        //水库注册号
        String rscd = null;
        //每个水库的appid和appkey
        String appid = null;
        String appkey = null;
        //初始化上传数据的标记为0--未上传，可以从站点的配置中获取初始的标记状态，对不需要上传的站点可以在站点信息中配置为无需上传
        int flag = 0x0;

        System.out.println("获取站点参数中的原始数据站号，stcd=" + stcd + " getParams=" + station.getParams());
        sourceStcd = JsonParserUtil.getString(station.getParams(), "run>原始数据站号");
        appid = JsonParserUtil.getString(station.getParams(), "run>id");
        appkey = JsonParserUtil.getString(station.getParams(), "run>key");
        System.out.println("获取站点参数中的原始数据站号，stcd=" + stcd + " sourceStcd=" + sourceStcd);
        if (StringUtils.isBlank(sourceStcd))
            sourceStcd = stcd;
        rscd = JsonParserUtil.getString(station.getParams(), "run>水库注册号");
        //获取站点配置中的flag配置
        flag = JsonParserUtil.getIntValue(station.getParams(), "run>flag", 0x0);
        //判断水库注册号、appid、appkey均不能为空
        if (StringUtils.isBlank(rscd))
            throw new QCPromptException("云南省汇集平台实时雨量数据生成器处理，水库注册号不能为空");
        if (StringUtils.isBlank(appid))
            throw new QCPromptException("云南省汇集平台实时雨量数据生成器处理，AppId不能为空");
        if (StringUtils.isBlank(appkey))
            throw new QCPromptException("云南省汇集平台实时雨量数据生成器处理，AppKey不能为空");
        //数据间隔频率，分钟
        int dataIntervalMinutes = JsonParserUtil.getIntValue(station.getParams(), "run>上报雨量频率分钟", 0x5);

        //步骤1.2.起止时间段
        if (beginTime.after(endTime))
            throw new QCPromptException("云南省汇集平台实时雨量数据生成器处理，起始时间不能大于截止时间");
        //增加判断时间不能距离现在时间太久
        //截止时间不能当前时间后1小时
        //开始时间不能小于当前时间前2月
        if (endTime.after(DateUtil.addMinutes(now, 60)))
            throw new QCPromptException("云南省汇集平台实时雨量数据生成器处理，截止时间不能大于当前时间后60分钟");
        if (beginTime.before(DateUtil.addMonths(now, -2)))
            throw new QCPromptException("云南省汇集平台实时雨量数据生成器处理，起始时间不能大于当前时间前2月");
        //时间点需要根据时段长度获取时段的起始时间
        int beginTimeMonth = DateUtil.getMonth(beginTime);
        //每个时间点都需要判断是否在汛期，可能出现一次出现4-5月或10-11月的数据情况
        Date beginHourTime = TimeSpanUtil.getBeginTime(beginTime, 5);
        if (beginTimeMonth > 10 || beginTimeMonth < 5)
            beginHourTime = TimeSpanUtil.getBeginTime(beginTime, 60);
        //根据传入的截止时间点计算时段末时间
        int endTimeMonth = DateUtil.getMonth(endTime);
        //每个时间点都需要判断是否在汛期，可能出现一次出现4-5月或10-11月的数据情况
        Date endHourTime = TimeSpanUtil.getEndTime(endTime, 5);
        if (endTimeMonth > 10 || endTimeMonth < 5)
            endHourTime = TimeSpanUtil.getEndTime(endTime, 60);
        //如果截止时间点大于当前时间，往前1个时段
        if (endHourTime.after(now)) {
            if (endTimeMonth > 10 || endTimeMonth < 5)
                endHourTime = DateUtil.addMinutes(endTime, -60);
            else endHourTime = DateUtil.addMinutes(endTime, -5);
        }

        //步骤1.3.目标数据库表信息判断和数据获取
        if (destDbTable == null)
            throw new QCPromptException("云南省汇集平台实时雨量数据生成器处理，目标数据库表对象不能为空");
        if (destDbTable.getTable() == null || StringUtils.isBlank(destDbTable.getTable().getCode()))
            throw new QCPromptException("云南省汇集平台实时雨量数据生成器处理，目标数据库表名称不能为空");
        if (destDbTable.getDb() == null || StringUtils.isBlank(destDbTable.getDb().getCon()))
            throw new QCPromptException("云南省汇集平台实时雨量数据生成器处理，目标数据库表连接信息不能为空");
        String destDbTableCode = destDbTable.getTable().getCode();
        String destConJsonString = destDbTable.getDb().getCon();
        String destDbUrl = JsonParserUtil.getString(destConJsonString, "url");
        String destDbUser = JsonParserUtil.getString(destConJsonString, "user");
        String destDbPassword = JsonParserUtil.getString(destConJsonString, "password");

        //步骤1.4.原始数据库表信息判断和数据获取
        if (srcDbTable == null)
            throw new QCPromptException("云南省汇集平台实时雨量数据生成器处理，原始数据库表对象不能为空");
        if (srcDbTable.getTable() == null || StringUtils.isBlank(srcDbTable.getTable().getCode()))
            throw new QCPromptException("云南省汇集平台实时雨量数据生成器处理，原始数据库表名称不能为空");
        if (srcDbTable.getDb() == null || StringUtils.isBlank(srcDbTable.getDb().getCon()))
            throw new QCPromptException("云南省汇集平台实时雨量数据生成器处理，原始数据库表连接信息不能为空");
        String srcDbTableCode = srcDbTable.getTable().getCode();
        String srcConJsonString = srcDbTable.getDb().getCon();
        String srcDbUrl = JsonParserUtil.getString(srcConJsonString, "url");
        String srcDbUser = JsonParserUtil.getString(srcConJsonString, "user");
        String srcDbPassword = JsonParserUtil.getString(srcConJsonString, "password");

        //实时雨量数据的目标时间和原始数据时间一致，不需要进行时间偏移处理；如果没有原始数据也能进行数据生成处理，默认雨量为0
        //2024-9-29，原始数据表由DeviceTimingDataRecord修改为MS_QZJ_SRCDATA；计讯接收软件接收到数据后写入DeviceTimingDataRecord，由触发器触发到MS_QZJ_SRCDATA
        //使用DeviceTimingDataRecord作为原始数据表时的查询条件和查询列
        //LinkedHashMap<Date, Double> sourceData = exchangeDataService.getData(srcDbUrl, srcDbUser, srcDbPassword, srcDbTableCode, "inserttime", "yczno", "timingvalue3", sourceStcd, beginHourTime, endHourTime);
        //使用MS_QZJ_SRCDATA作为原始数据表时的查询条件和查询列
        //查询原始数据除站点编码和时间段外还需要的其他等于条件
        Map<String, Object> otherEqualWhereData = new HashMap<>();
        otherEqualWhereData.put("DATATYPE", "rain");
        //2024-12-26，查询原始数据的起始时间要对应时间点往前1个时段（5分钟或60分钟）
        Date getSourceDataBeginTime =DateUtil.addMinutes(beginTime,-5);
        if (beginTimeMonth > 10 || beginTimeMonth < 5)
            getSourceDataBeginTime =DateUtil.addMinutes(beginTime,-60);
        LinkedHashMap<Date, Double> sourceData = exchangeDataService.getData(srcDbUrl, srcDbUser, srcDbPassword, srcDbTableCode, "TM", "STCD", "VALUE", sourceStcd, getSourceDataBeginTime, endHourTime, otherEqualWhereData);
        if (sourceData == null || sourceData.size() < 0x1)
            sourceData = new LinkedHashMap<>();//解决空指针异常：sourceData为null，sourceData.put直接会报出空指针异常
        //查询原始雨量数据的起始时间需要往前，需要计算起始时间点的雨量值，由于无法确定往前多久，需要单独进行一次查询;时间点使用起始时间往前1秒
        //使用DeviceTimingDataRecord作为原始数据表时的查询条件和查询列
        //LinkedHashMap<Date, Double> latestSourceData = exchangeDataService.getLatestData(srcDbUrl, srcDbUser, srcDbPassword, srcDbTableCode, "inserttime", "yczno", "timingvalue3", sourceStcd, DateUtil.addSeconds(getSourceDataBeginTime, -1));
        //使用MS_QZJ_SRCDATA作为原始数据表时的查询条件和查询列
        LinkedHashMap<Date, Double> latestSourceData = exchangeDataService.getLatestData(srcDbUrl, srcDbUser, srcDbPassword, srcDbTableCode, "TM", "STCD", "VALUE", sourceStcd, DateUtil.addSeconds(getSourceDataBeginTime, -1), otherEqualWhereData);
        if (latestSourceData != null && latestSourceData.size() > 0x0) {
            //最新1条数据查询使用top 1 order by tm desc查询，只需要添加查询结果的第一条数据
            Map.Entry<Date, Double> firstEntry = latestSourceData.entrySet().iterator().next();
            //判断最新的一条雨量数据是否已经在查询的原始数据中存在，如果已经存在不需要重复添加
            if (sourceData != null && sourceData.size() > 0x0) {
                if (!sourceData.keySet().contains(firstEntry.getKey()))
                    sourceData.put(firstEntry.getKey(), firstEntry.getValue());
            } else {
                sourceData.put(firstEntry.getKey(), firstEntry.getValue());
            }
        }

        System.out.println("云南省汇集平台实时雨量数据生成器处理，stcd=" + stcd + "，本次查询原始数据的时间段为：" + DateUtil.getDateTimeString(getSourceDataBeginTime) + "~~~" + DateUtil.getDateTimeString(endHourTime) + "获取到原始数据共：" + sourceData.size() + "条");

        ///传入的起始时间就是要生成数据的第1个时间点，不需要往后1个时段
        Date date = beginHourTime;
        //需要从计算的时段初开始时间点到截止时间点，包含截止时间点；判断条件为：后移的时间点不大于截止时间点
        while (!date.after(endHourTime)) {
            //降雨量的累计分钟数（步长）需要根据汛枯期时间得到
            int dateMonth = DateUtil.getMonth(date);
            if (dateMonth > 10 || dateMonth < 5)
                dataIntervalMinutes = 60;
            else
                dataIntervalMinutes = 5;
            //降雨量
            double totalRain = Double.NaN;
            Date totalRainTime = null;
            double beforeTotalRain = Double.NaN;
            Date beforeTotalRainTime = null;
            if (sourceData != null && sourceData.size() > 0x0) {
                //for (Map.Entry<Date, Double> entry : sourceData.entrySet()) {
                //    System.out.println("处理水库降雨量，date=" + date.toString() + " sourceData tm=" + DateUtil.getDateTimeString(entry.getKey()) + " value=" + entry.getValue());
                //}

                Date finalDate = date;
                //取对应时间点
                if (sourceData.containsKey(finalDate)) {
                    totalRain = sourceData.get(finalDate);
                    totalRainTime = finalDate;
                }

                //如果原始数据没有值，按时间找最近一个
                if (Double.isNaN(totalRain)) {
                    //判断条件：时间不在当前时间点之后；top 1 <= tm order by tm desc
                    List<Date> lessThanDates = sourceData.keySet().stream().filter(p -> p.after(finalDate) == false).sorted(Comparator.comparingLong(Date::getTime)).collect(Collectors.toList());
                    if (lessThanDates != null && lessThanDates.size() > 0x0) {
                        //小于等于时间点的结果升序排列，取最后一个
                        totalRainTime = lessThanDates.get(lessThanDates.size() - 1);
                        totalRain = sourceData.get(totalRainTime);
                        //System.out.println("处理水库降雨量，date=" + date.toString() + " 取截止时间累计降雨量数据时间=" + DateUtil.getDateTimeString(lessThanDates.get(lessThanDates.size() - 1)) + " 累计降雨量值=" + totalRain);
                    }
                }
                //如果当前时间点的累计雨量数据有值，往后间隔分钟数提取前一条雨量数据；如果当前时间点的累计雨量没有值，不能进行雨量计算；
                if (!Double.isNaN(totalRain)) {
                    //前一条雨量数据
                    Date beforeDate = DateUtil.addMinutes(finalDate, -dataIntervalMinutes);
                    //System.out.println("处理水库降雨量，date=" + date.toString() + " 取前一条累计降雨量数据时间=" + DateUtil.getDateTimeString(beforeDate));
                    //取对应时间点
                    if (sourceData.containsKey(beforeDate)) {
                        beforeTotalRain = sourceData.get(beforeDate);
                        beforeTotalRainTime = beforeDate;
                    }

                    //如果原始数据没有值，按时间找最近一个
                    if (Double.isNaN(beforeTotalRain)) {
                        //判断条件：时间不在时间点之后最近的时间；top 1 <= tm order by tm desc
                        List<Date> lessThanDates = sourceData.keySet().stream().filter(p -> p.after(beforeDate) == false).sorted(Comparator.comparingLong(Date::getTime)).collect(Collectors.toList());
                        if (lessThanDates != null && lessThanDates.size() > 0x0) {
                            beforeTotalRainTime = lessThanDates.get(lessThanDates.size() - 1);
                            beforeTotalRain = sourceData.get(beforeTotalRainTime);
                        }
                    }
                    //如果原始数据没有值，取原始数据中的第1个值作为默认
                    if (Double.isNaN(beforeTotalRain)) {
                        beforeTotalRainTime = sourceData.keySet().stream().collect(Collectors.toList()).get(0x0);
                        beforeTotalRain = sourceData.get(beforeTotalRainTime);
                    }
                    //System.out.println("处理水库降雨量，date=" + date.toString() + " 取前一条累计降雨量数据时间=" + DateUtil.getDateTimeString(beforeDate));
                }
            }
            //计算雨量值
            double rainValue = 0.0f;
            //需要前后2个雨量值均有值，并且前一个值小于等于后一个值
            if (!Double.isNaN(beforeTotalRain) && !Double.isNaN(totalRain) && beforeTotalRain <= totalRain) {
                rainValue = totalRain - beforeTotalRain;
            }
            System.out.println("云南省汇集平台实时雨量数据生成器处理，stcd=" + stcd + "，本次生成数据时间为：" + DateUtil.getDateTimeString(date) + "，计算降雨量="
                    + rainValue + "，起始雨量时间=" + (beforeTotalRainTime == null ? "null" : DateUtil.getDateTimeString(beforeTotalRainTime))
                    + "，起始雨量值=" + beforeTotalRain + "，截止雨量时间=" + (totalRainTime == null ? "null" : DateUtil.getDateTimeString(totalRainTime)) + "，截止雨量值=" + totalRain);
            //写入数据
            Map<String, Object> whereData = new HashMap<>();
            whereData.put("stcd", stcd);
            whereData.put("tm", date);
            //判断数据是否存在，如果存在仅对数据值和上传标记进行修改；如果重新写入会因为写入时间延后导致统计准确率错误
            if (SqlRunnerUtil.hasExistData(destDbUrl, destDbUser, destDbPassword, destDbTableCode, whereData)) {
                //已有相同站点、相同时间的数据存在
                Map<String, Object> updateData = new HashMap<>();
                updateData.put("dr", dataIntervalMinutes);
                updateData.put("p", rainValue);
                //updateData.put("intm", now);
                updateData.put("flag", flag);

                boolean updateDataResult = SqlRunnerUtil.updateData(destDbUrl, destDbUser, destDbPassword, destDbTableCode, updateData, whereData);
            } else {
                //不存在相同数据，写入
                Map<String, Object> insertData = new HashMap<>();
                insertData.put("stcd", stcd);
                insertData.put("tm", date);
                insertData.put("dr", dataIntervalMinutes);
                insertData.put("p", rainValue);
                insertData.put("prcd", " ");
                insertData.put("intm", now);
                insertData.put("flag", flag);
                if (appid != null) insertData.put("appid", appid);
                if (appkey != null) insertData.put("appkey", appkey);

                boolean insertDataResult = SqlRunnerUtil.insertData(destDbUrl, destDbUser, destDbPassword, destDbTableCode, insertData);
            }

            //每个时间点都需要判断是否在汛期，可能出现一次出现4-5月或10-11月的数据情况
            int latestDataMonth = DateUtil.getMonth(date);
            if (latestDataMonth > 10 || latestDataMonth < 5)
                date = DateUtil.addMinutes(date, 60);
            else
                date = DateUtil.addMinutes(date, 5);
        }

        return null;
    }
}
